﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

namespace Jemgine.Renderer
{
	public class RenderContext
	{
		public Jemgine.Data.MapData Map;
		public Texture2D LineTexture;
		public Texture2D PointTexture;
        public Texture2D SolidPointTexture;
        public Texture2D SolidTexture;
		public Renderer Renderer;

        private RenderTarget2D ShadowBuffer;
        private RenderTarget2D LightBuffer;

        public bool Normals { get; set; }
        public Vector4 Color { get; set; }

        private Stack<Matrix> MatrixStack = new Stack<Matrix>();

        public RenderContext(GraphicsDevice Device)
        {
            MatrixStack.Push(Matrix.Identity);
            SolidTexture = new Texture2D(Device, 16, 16);
            SolidTexture.SetData(Enumerable.Repeat(Microsoft.Xna.Framework.Color.White, 16 * 16).ToArray());

            ShadowBuffer = new RenderTarget2D(Device, Device.Viewport.Width, Device.Viewport.Height);
            LightBuffer = new RenderTarget2D(Device, Device.Viewport.Width, Device.Viewport.Height);
        }

        public void PushMatrix(Matrix M)
        {
            MatrixStack.Push(M * MatrixStack.Peek());
        }

        public void PushCleanMatrix(Matrix M)
        {
            MatrixStack.Push(M);
        }

        public void PopMatrix()
        {
            if (MatrixStack.Count <= 1) throw new InvalidOperationException();
            MatrixStack.Pop();
        }

        public Matrix TopMatrix { get { return MatrixStack.Peek(); } }

        public void DrawSkeleton(Jemgine.Data.Skeleton _skeleton)
        {
            foreach (var Bone in _skeleton.Bones)
            {
                Vector2 End = Vector2.Transform(Bone.Position, Bone.Transform);

                if (Bone.Parent != -1)
                {
                    Vector2 Start = Vector2.Transform(_skeleton.Bones[Bone.Parent].Position,
                        _skeleton.Bones[Bone.Parent].Transform);
                    DrawLine(Start, End, 0.5f);
                }
                DrawPoint(End);
            }
        }


		public void DrawSprite(Texture2D Sprite, Vector2 Position, Vector2 Size, float Angle, bool Flip, bool Add)
		{
			Renderer.DrawSprite(MatrixStack.Peek(), Sprite, Color, Position, Size, Angle, Flip, Add);
		}

        public void DrawOrientedSprite(Texture2D Sprite, Vector2 Position, Vector2 Orientation)
        {
            Renderer.DrawOrientedSprite(MatrixStack.Peek(), Sprite, Color, Position, Orientation);
        }

		public void DrawSpriteOutline(Vector2 Position, Vector2 Size, float Angle)
		{
            Renderer.DrawSpriteOutline(MatrixStack.Peek(), LineTexture, Color, Position, Size, Angle, 0.2f);
		}

		

		public Vector2 ElementPosition(int Element)
		{
			if (Element < 0 || Element >= Map.Elements.Count) return Vector2.Zero;
            return Map.Elements[Element].GetPosition();
         
		}

		public void DrawLine(Vector2 Start, Vector2 End, float Width)
		{
            Renderer.DrawLine(MatrixStack.Peek(), LineTexture, Start, End, Color, Width);
		}

		public void DrawPoint(Vector2 where)
		{
            Renderer.DrawSprite(MatrixStack.Peek(), PointTexture, Color, where, new Vector2(1, 1), 0, false, false);
		}

        public void DrawElementLine(Jemgine.Data.Line Spline, Texture2D Texture)
        {
            if (Texture == null)
            {
                Texture = LineTexture;
                if (Spline.RoundEnds)
                {
                    Renderer.DrawSprite(MatrixStack.Peek(), SolidPointTexture, Vector4.One, Spline[0].Position,
                        new Vector2(Spline.Width * 0.5f, Spline.Width * 0.5f), 0, false, false);
                    Renderer.DrawSprite(MatrixStack.Peek(), SolidPointTexture, Vector4.One, Spline[1].Position,
                        new Vector2(Spline.Width * 0.5f, Spline.Width * 0.5f), 0, false, false);
                }
            }

            Renderer.DrawElementLine(MatrixStack.Peek(), Texture, Vector4.One, Spline.Decomposed);
        }

        public void DrawSpline(Jemgine.Data.Spline Spline, Texture2D Texture)
        {
            if (Texture == null) Texture = LineTexture;
                Renderer.DrawSpline(MatrixStack.Peek(), Texture, Vector4.One, Spline.Decomposed, Spline.Segments);
        }

        public void DrawQuad(Jemgine.Data.Quad Polygon, Texture2D Texture)
        {
            if (Texture != null)
            {
                Renderer.TexturedQuad(MatrixStack.Peek(), Polygon, Texture);
            }

            for (int I = 0; I < Polygon.Count; ++I)
            {
                Vector2 B = Polygon[I].Position;
                Vector2 A = Polygon[I == 0 ? Polygon.Count - 1 : I - 1].Position;
                DrawLine(A, B, 0.1f);

                Vector2 EdgeNormal = Math.Vector.EdgeNormal(A, B);
                Vector2 Mid = (A + B) / 2.0f;

                Renderer.DrawLine(MatrixStack.Peek(),
                    LineTexture, Mid, Mid + (EdgeNormal),
                    Microsoft.Xna.Framework.Color.Green.ToVector4(), 0.2f);

            }

        }

		public void DrawEditorPolygon(Jemgine.Data.EditorPolygon Polygon, Texture2D Texture, bool _drawDecomposed)
		{
            if (Texture != null)
            {
                if (Polygon.DecomposedRuntimePolygons == null) Polygon.UpdateDecomposedCache();
                foreach (var Poly in Polygon.DecomposedRuntimePolygons)
                    Renderer.TexturedRuntimePolygon(MatrixStack.Peek(), Poly, Texture, Polygon.Alignment);
            }

            if (_drawDecomposed)
            {
                if (Polygon.DecomposedRuntimePolygons == null) Polygon.UpdateDecomposedCache();
                foreach (var Poly in Polygon.DecomposedRuntimePolygons)
                    DrawDecomposedPolygon(Poly, null);
            }                

            for (int I = 0; I < Polygon.Count; ++I)
                DrawSprite(
                    SolidPointTexture, 
                    Polygon[I].Position, 
                    new Vector2(0.05f, 0.05f),
                    0.0f, 
                    false, false);

			for (int I = 0; I < Polygon.Count; ++I)
			{
				Vector2 B = Polygon[I].Position;
				Vector2 A = Polygon[I == 0 ? Polygon.Count - 1 : I - 1].Position;
				DrawLine(A, B, 0.1f);
				
					Vector2 EdgeNormal = Math.Vector.EdgeNormal(A, B);
					Vector2 Mid = (A + B) / 2.0f;

                    Renderer.DrawLine(MatrixStack.Peek(),
						LineTexture, Mid, Mid + (EdgeNormal),
						Microsoft.Xna.Framework.Color.Green.ToVector4(), 0.2f);

			}
                        
		}

        public void DrawRuntimePolygon(Jemgine.Data.EditorPolygon Polygon, Texture2D Texture, bool _drawDecomposed)
        {
            if (Texture != null)
            {
                if (Polygon.DecomposedRuntimePolygons == null) Polygon.UpdateDecomposedCache();
                foreach (var Poly in Polygon.DecomposedRuntimePolygons)
                    Renderer.TexturedRuntimePolygon(MatrixStack.Peek(), Poly, Texture, Polygon.Alignment);
            }

        }

        public void DrawDecomposedPolygon(Jemgine.Data.RuntimePolygon Polygon, Texture2D Texture)
        {

            for (int I = 0; I < Polygon.Count; ++I)
            {
                Vector2 B = Polygon[I];
                Vector2 A = Polygon[I == 0 ? Polygon.Count - 1 : I - 1];
                DrawLine(A, B, 0.1f);

                if (Normals)
                {
                    Vector2 EdgeNormal = Math.Vector.EdgeNormal(A, B);
                    Vector2 Mid = (A + B) / 2.0f;

                    Renderer.DrawLine(MatrixStack.Peek(),
                        LineTexture, Mid, Mid + (EdgeNormal),
                        Microsoft.Xna.Framework.Color.Green.ToVector4(), 0.2f);
                }

            }

            for (int I = 0; I < Polygon.Count; ++I)
                DrawSprite(SolidPointTexture, Polygon[I], new Vector2(0.1f, 0.1f), 0.0f, false, false);

        }

        public void DrawOBB(Jemgine.Math.OBB OBB, float LineWidth)
        {
            var Points = OBB.GetPoints();
            DrawLine(Points[0], Points[1], LineWidth);
            DrawLine(Points[1], Points[2], LineWidth);
            DrawLine(Points[2], Points[3], LineWidth);
            DrawLine(Points[3], Points[0], LineWidth);
        }

		public void DrawBox(Vector2 Min, Vector2 Max, float LineWidth)
		{
			Vector2 P0 = Min;
			Vector2 P1 = new Vector2(Max.X, Min.Y);
			Vector2 P2 = Max;
			Vector2 P3 = new Vector2(Min.X, Max.Y);

			DrawLine(P0, P1, LineWidth);
			DrawLine(P1, P2, LineWidth);
			DrawLine(P2, P3, LineWidth);
			DrawLine(P3, P0, LineWidth);
		}

        public void CleanMatrixStack()
        {
            while (MatrixStack.Count > 1) MatrixStack.Pop();
        }

        public void DrawGrid(Vector2 Origin, Vector2 XAxis, Vector2 YAxis, Vector2 Extents, float LineWidth)
        {
            Vector2 Start = Origin - (XAxis * Extents.X / 2) - (YAxis * Extents.Y / 2);

            int Cols = (int)(Extents.X) + 1;
            int Rows = (int)(Extents.Y) + 1;

            Vector2 RowStart = Start;
            Vector2 RowEnd = Start + (XAxis * Extents.X);
            //Draw rows
            for (int Y = 0; Y < Rows; ++Y)
            {
                Renderer.DrawLine(MatrixStack.Peek(), SolidTexture, RowStart, RowEnd, Color, LineWidth);
                RowStart += YAxis;
                RowEnd += YAxis;
            }

             Vector2 ColStart = Start;
            Vector2 ColEnd = Start + (YAxis * Extents.Y);
            //Draw cols
            for (int X = 0; X < Cols; ++X)
            {
                Renderer.DrawLine(MatrixStack.Peek(), SolidTexture, ColStart, ColEnd, Color, LineWidth);
                ColStart += XAxis;
                ColEnd += XAxis;
            }
        }

        public void DrawCircle(Vector2 Position, float Radius, int Segments, float LineWidth)
        {
            Vector2 StartPoint = new Vector2(0, Radius);
            Matrix Rot = Matrix.CreateRotationZ(MathHelper.ToRadians(360.0f / (float)Segments));
            Vector2 EndPoint = Vector2.Transform(StartPoint, Rot);
            PushMatrix(Matrix.CreateTranslation(Position.X, Position.Y, 0.0f));

            for (int i = 0; i < Segments; ++i)
            {
                DrawLine(StartPoint, EndPoint, LineWidth);
                StartPoint = EndPoint;
                EndPoint = Vector2.Transform(EndPoint, Rot);
            }

            PopMatrix();
        }

        public void DrawCircle(Jemgine.Data.Circle Circle)
        {
            DrawCircle(Circle.Position, Circle.Radius, Circle.Segments, 0.1f);
        }
    }
}
